package com.gitee.wsl.func.filter.bloomfilter.bean

import com.gitee.wsl.func.filter.bloomfilter.bean.BitMaps.contains
import com.gitee.wsl.func.filter.bloomfilter.bean.BitMaps.newBitMap
import com.gitee.wsl.func.filter.bloomfilter.bean.BitMaps.numberOfBitMaps
import com.gitee.wsl.func.filter.bloomfilter.bean.BitMaps.set
import com.gitee.wsl.func.predicate.IntPredicate
import com.gitee.wsl.text.format.format

/**
 * A convenience class for Hasher implementations to filter out duplicate indices.
 *
 *
 * *If the index is negative the behavior is not defined.*
 *
 *
 * This is conceptually a unique filter implemented as an [IntPredicate].
 *
 * @since 4.5.0-M1
 */
class IndexFilter private constructor(
    shape: Shape,
    private val consumer: IntPredicate
) {
    /**
     * An IndexTracker implementation that uses an array of integers to track whether or not a
     * number has been seen. Suitable for Shapes that have few hash functions.
     * @since 4.5.0
     */
    internal class ArrayTracker(shape: Shape) : IntPredicate {
        private val seen: IntArray = IntArray(shape.numberOfHashFunctions)
        private var populated = 0

        public override fun test(t: Int): Boolean {
            if (t < 0) {
                throw IndexOutOfBoundsException("number may not be less than zero. $t")
            }
            for (i in 0..<populated) {
                if (seen[i] == t) {
                    return false
                }
            }
            seen[populated++] = t
            return true
        }
    }

    /**
     * An IndexTracker implementation that uses an array of bit maps to track whether or not a
     * number has been seen.
     */
    internal class BitMapTracker(shape: Shape) : IntPredicate {
        private val bits: LongArray = newBitMap(shape)

        public override fun test(t: Int): Boolean {
            val retval = !contains(bits, t)
            set(bits, t)
            return retval
        }
    }

    private val tracker: IntPredicate

    private val size: Int = shape.numberOfBits

    /**
     * Creates an instance optimized for the specified shape.
     *
     * @param shape The shape that is being generated.
     * @param consumer The consumer to accept the values.
     */
    init {
        if (numberOfBitMaps(shape) * Long.SIZE_BYTES < shape.numberOfHashFunctions.toLong() * Int.SIZE_BYTES) {
            this.tracker = BitMapTracker(shape)
        } else {
            this.tracker = ArrayTracker(shape)
        }
    }

    /**
     * Test if the number should be processed by the `consumer`.
     *
     *
     * If the number has *not* been seen before it is passed to the `consumer` and the result returned.
     * If the number has been seen before the `consumer` is not called and `true` returned.
     *
     *
     * *If the input is not in the range [0,size) an IndexOutOfBoundsException exception is thrown.*
     *
     * @param number the number to check.
     * @return `true` if processing should continue, `false` otherwise.
     */
    fun test(number: Int): Boolean {
        if (number >= size) {
            throw IndexOutOfBoundsException(String.format("number too large %d >= %d", number, size))
        }
        if (tracker.test(number)) {
            return consumer.test(number)
        }
        return true
    }

    companion object {
        /**
         * Creates an instance optimized for the specified shape.
         *
         * @param shape The shape that is being generated.
         * @param consumer The consumer to accept the values.
         * @return an IndexFilter optimized for the specified shape.
         */
        fun create(shape: Shape, consumer: IntPredicate): IntPredicate {
            return IntPredicate { number: Int -> IndexFilter(shape, consumer).test(number) }
        }
    }
}